Skip to content

Conversation

@Fe-r-oz
Copy link
Contributor

@Fe-r-oz Fe-r-oz commented May 28, 2025

Checklist

Thank you for contributing to QuantumToolbox.jl! Please make sure you have finished the following tasks before opening the PR.

  • Please read Contributing to Quantum Toolbox in Julia.
  • Any code changes were done in a way that does not break public API.
  • Appropriate tests were added and tested locally by running: make test.
  • Any code changes should be julia formatted by running: make format.
  • All documents (in docs/ folder) related to code changes were updated and able to build locally by running: make docs. PS. make docs crashes my PC after it's ran for some time but I made sure to add the necessary documentation.
  • (If necessary) the CHANGELOG.md should be updated (regarding to the code changes) and built by running: make changelog.

Request for a review after you have completed all the tasks. If you have not finished them all, you can also open a Draft Pull Request to let the others know this on-going work.

Description

Edit: This PR fixes #461. The goal is to add the bloch rendering in Julia which have a similar user experience as qutip

Additional context

Edit: Attaching the plots that reproduce the entire qutip documentation documentation:

Add a Single Data Point

display

Add a Single Vector

display

display

Add Arc, Line, and Vector

display

Add Multiple Points

display

display

display

display

We can also use GLMakie

Screenshot_select-area_20250604203219

@codecov
Copy link

codecov bot commented May 28, 2025

Codecov Report

Attention: Patch coverage is 84.92063% with 38 lines in your changes missing coverage. Please review.

Project coverage is 93.67%. Comparing base (fbe80da) to head (fe446e7).
Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
ext/QuantumToolboxMakieExt.jl 86.20% 28 Missing ⚠️
src/visualization.jl 79.59% 10 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #472      +/-   ##
==========================================
- Coverage   94.37%   93.67%   -0.70%     
==========================================
  Files          50       50              
  Lines        3162     3414     +252     
==========================================
+ Hits         2984     3198     +214     
- Misses        178      216      +38     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@albertomercurio
Copy link
Member

albertomercurio commented May 28, 2025

Hi @Fe-r-oz, thank you for contributing!

I will give a look more in detail later. For the moment just a few comments.

Could you try to reduce the number of lines representing the sphere, and also make more opacity? If I see the qutip version there is probably more transparency and less lines

image

Also, in your 5th figure, the arch is not an arc.

Finally, I don't think we need the Latexstrings and Colors dependencies, as they should be directly implemented in Makie.jl

@albertomercurio
Copy link
Member

Thank you, just a few other things before checking the code.

The sphere seems to be rotated compared to the one of QuTiP. Indeed, in QuTiP we have that x points almost outside, while here it is -y doing so. Better to align to QuTiP.

Since you added the bloch.jl file in the ext folder, it is better to embed everything inside a QuantumToolboxMakieExt folder.

The vectors tips seem to go outside the sphere.

Better to show all the examples showed in the qutip documentation, in order to check all the properties.

@Fe-r-oz
Copy link
Contributor Author

Fe-r-oz commented May 29, 2025

Thank you very much for your comments and suggestions! Turning it to draft to incorporate all the suggested changes.

@Fe-r-oz Fe-r-oz marked this pull request as draft May 29, 2025 09:21
@Fe-r-oz
Copy link
Contributor Author

Fe-r-oz commented May 29, 2025

Some basic plots from qutip documentation.

Empty Sphere:

julia> b = Bloch()
julia> fig, ax = render(b)
julia> fig

display

Adding a point and vector to the above figure:

julia> b = Bloch()
julia> pnt = [1/√3, 1/√3, 1/√3]
julia> add_points!(b, pnt);
julia> add_vectors!(b, [[1, 0, 0], [0, 1, 0]]);
julia> fig, ax = render(b)

display

Adding Vectors
display

Adding lines and arcs
display

code for lines, arc and vectors

julia> vec = [[1, 0, 0], [0, 1, 0], [0, 0, 1]];
julia> add_vectors!(b, vec);
julia> fig, ax = render(b)
julia> fig
julia> add_line!(b, [1,0,0], [0,1,0]);
julia> fig
julia> add_arc!(b, [0,1,0],[0,0,1])
julia> fig, ax = render(b)
julia> fig
julia> add_arc!(b, [1,0,0],[0,1,0])
julia> fig, ax = render(b)
julia> fig
julia> add_arc!(b, [0,1,0],[0,0,1])
julia> fig, ax = render(b)
julia> fig
julia> add_arc!(b, [0,1,0],[1,0,0])
julia> fig, ax = render(b)
julia> fig

Adding states from QuantumToolbox

julia> ρ = 0.7*ket2dm(basis(2,0)) + 0.3*ket2dm(basis(2,1));
julia> fig, ax = plot_bloch(ρ)
julia> display(fig)

display

Adding scatter of points:


julia> th = range(0, 2π; length=20);
julia> xz = zeros(20);
julia> yz = sin.(th);
julia> zz = cos.(th);
julia> points = hcat(xz, yz, zz)';
julia> add_points!(b, Matrix(points));
julia> fig, ax = render(b);
julia> fig
julia> fig, ax = render(b);
julia> fig
julia> xp = cos.(th);
julia> yp = sin.(th);
julia> zp = zeros(20);
julia> pnts = [xp, yp, zp] ;
julia> pnts = Matrix(hcat(xp, yp, zp)');
julia> add_points!(b, pnts);
julia> fig, ax = render(b);
julia> fig

display

Arc, Lines and Vectors

clear!(b)
vec = [[1, 0, 0], [0, 1, 0], [0, 0, 1]];
add_vectors!(b, vec);
add_line!(b, [1,0,0], [0,1,0]);
add_arc!(b, [1, 0, 0], [0, 1, 0], [0, 0, 1])
fig, _ = render(b)
fig

display

Add Quantum States

clear!(b)
x = basis(2, 0) + basis(2, 1)
y = basis(2, 0) - im * basis(2, 1)
z = basis(2, 0)
b = Bloch()
add_states!(b, [x, y, z])
fig, _ = render(b)
fig

display

@Fe-r-oz Fe-r-oz marked this pull request as ready for review May 30, 2025 15:22
| [`plot_wigner`](@ref) | [Wigner quasipropability distribution](https://en.wikipedia.org/wiki/Wigner_quasiprobability_distribution) |
| [`plot_fock_distribution`](@ref) | [Fock state](https://en.wikipedia.org/wiki/Fock_state) distribution |
| [`plot_fock_distribution`](@ref) | [Fock state](https://en.wikipedia.org/wiki/Fock_state) distribution |
| [`plot_bloch`](@ref) | [Qutip's Plotting on the Bloch Sphere](https://qutip.readthedocs.io/en/stable/guide/guide-bloch.html) |
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would remove Qutip. Just "Plotting on the Bloch Sphere"

```@setup Bloch_sphere_rendering
using QuantumToolbox
using Makie
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your don't need to import also Makie. CairoMakie is enough.


When studying the dynamics of a two-level system, it's often convenient to visualize the state of the system by plotting the state vector or density matrix on the Bloch sphere.

In [QuantumToolbox.jl](https://qutip.org/QuantumToolbox.jl/), this can be done using the [`Bloch`](@ref) or [`plot_bloch`](@ref) methods that provide same syntax as [QuTiP](https://github.com/qutip/qutip).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe directly link to the Bloch Sphere page in qutip

https://qutip.readthedocs.io/en/stable/guide/guide-bloch.html


using QuantumToolbox
using LinearAlgebra
using Makie
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to import only the necessary functions, if they are not a lot.

function QuantumToolbox.add_line!(
b::QuantumToolbox.Bloch,
start::QuantumObject{<:Union{Ket,Bra,Operator}},
endp::QuantumObject{<:Union{Ket,Bra,Operator}};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

endp is not a nice name. Better end or change both to start_point and end_point.

Comment on lines 463 to 464
b.fig = fig
b.ax = Makie.Axis3(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even if the equivalent qutip class embeds also the fig and ax objects. I think that they are not needed in the Bloch struct. render would just generate fig and ax, without storing the in the Bloch object.

location[] = b.ax
end
end
Makie.empty!(b.ax)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a few things here.

The code is quite long. I would first write some explanation comments, and divide each operation as a separate function.

Moreover, it is not necessary to repeat Makie.functionnif the function is imported with using Makie: function

frame_color::String
frame_width::Int
point_default_color::Vector{String}
point_color::Vector{Any}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would avoid to use Any, as it makes type instabilities.

Comment on lines 139 to 140
fig::Any
ax::Any
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I said before. fig and ax are not necessary to be stored in the Bloch structure. Once one calls render, they are generated and the user can operate on them.

fig::Any
ax::Any

function Bloch()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of defining this function, I would use Base.@kwdef`

https://docs.julialang.org/en/v1/base/base/#Base.@kwdef

@albertomercurio
Copy link
Member

Hi @Fe-r-oz, thank you. I have wrote some comments on the code. I have a few minor ones, which I will do after you apply these changes.

Anyhow, I see that now the arrows are too short. Isn't it?

@Fe-r-oz Fe-r-oz force-pushed the fa/resolve branch 2 times, most recently from 43fbcf3 to 5b8c30a Compare June 2, 2025 10:37
@albertomercurio
Copy link
Member

Tell me when it is ready for review

@Fe-r-oz
Copy link
Contributor Author

Fe-r-oz commented Jun 2, 2025

Thank you so very much for your guidance!! Please help review this PR, Thank you!! I have hopefully incorporated all of the suggestions! Indeed, the vector size was approx 80% of unit radius, made it 90% of the radius, added LaTex based labelling of axes! Looking forward the the next round of comments!

display

@Fe-r-oz Fe-r-oz requested a review from albertomercurio June 2, 2025 12:39
"""
function _draw_reference_circles(ax)
wire_color = RGBAf(0.5, 0.5, 0.5, 0.4)
φ = range(0, 2π, length = 100)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By dividing into functions helped me a lot on the readability. What happens if we reduce the length of φ for example to 10? Do we reduce the number of circles? If so, then I prefer to have less circles, as said a few comments ago. Could you plot an example here?

Copy link
Contributor Author

@Fe-r-oz Fe-r-oz Jun 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your patience and for raising this important point again. Sorry for the delay in addressing this suggestion properly. Regarding the implementation, it appears that Makie.GeometryBasics.Sphere (center + radius) wasn’t suitable for our needs, so I’ve built a custom sphere via surface! instead. Please let me know your feedback!!

The previous approach:
when φ = 10
display

when φ = 100
display

The new approach:

Edit: Temporarily using this color scheme as the default.

display

display

display

display

display

Since we are using surface!, lighter tones combined with transparency may reveal the underlying mesh grid of the surface!.

display

display

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How did you generate the sphere with the other approach? Could the mesh! function together with Sphere work?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I generated the sphere using a custom parametric mesh via the surface! function, rather than relying on the built-in Sphere type with mesh!. This approach gives us precise control over the number and placement of longitude and latitude lines. The sphere is explicitly constructed from parametric equations in spherical coordinates.

While Makie’s Sphere type (an alias for HyperSphere{3, T}) is convenient for basic 3D geometry, it has certain design constraints. For example, mesh generation is fixed internally using coordinates(s::Sphere, nvertices=24), which samples spherical angles using a hardcoded resolution. Unfortunately, it appears that nvertices isn't configurable through the mesh! interface. I also explored modifying the Sphere's mesh parameters directly, but it seems this flexibility isn’t currently exposed through the high-level API.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But can't you play with transparency and alpha on the mesh? What I don't like of this surface method is that I can see the underlying mesh grid.

Copy link
Contributor Author

@Fe-r-oz Fe-r-oz Jun 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can indeed tweak the alpha and transparency settings to reduce or eliminate the appearance of the mesh grid when using surface!. The last two examples I shared earlier were mostly exploratory, just to get a feel for different visual effects while playing with color and opacity settings. I also don't like the underlying grid look which was likely caused by the alpha and transparency settings.

For example, tweaking alpha and transparency settings, we get:

display
display

Edit: increasing the resolution, the mesh grid disappears. Some examples;

display
display

We can refine this method further; I will explore additional tweaks and see how to achieve a cleaner result with surface! Please let me know your feedback! Thank you!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the first image is really nice. How did you get that?

Copy link
Contributor Author

@Fe-r-oz Fe-r-oz Jun 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! There are two ways of generating the first image. We can use both sphere + mesh or surface!. I generated the above images via surface!. But I have been following through your comment and as you mentioned, we can do the same with sphere+mesh (figure attached below). The goal is to disappear the mesh grid, and draw the few latitude and longitude lines ourselves.

Sphere+mesh approach:
display

Edit: more polished version

display

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I like it. This could be the definitive one.

Comment on lines 606 to 608
([Point3f(0, -1.01, 0), Point3f(0, 1.01, 0)], "y"), # Y-axis
([Point3f(-1.01, 0, 0), Point3f(1.01, 0, 0)], "x"), # X-axis
([Point3f(0, 0, -1.01), Point3f(0, 0, 1.01)], "z"), # Z-axis
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why 1.01 and not simply 1.0? You can send some examples here in case.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, they are also not aligned to the axes. They should be the continuation of the corresponding axis

Comment on lines 494 to 501
_draw_bloch_sphere(ax)
_draw_reference_circles(ax)
_draw_axes(ax)
_plot_points(b, ax)
_plot_lines(b, ax)
_plot_arcs(b, ax)
_plot_vectors(b, ax)
_add_labels(ax)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fantastic, thanks. Could you just add the ! symbol to tell this converts ax inplace?

Comment on lines 766 to 769
(Point3f(1.04, 0, 0), L"\textbf{y}"),
(Point3f(0, -1.10, 0), L"\textbf{x}"),
(Point3f(0, 0, -1.10), L"\mathbf{|1\rangle}"),
(Point3f(0, 0, 1.08), L"\mathbf{|0\rangle}"),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The labels are a bit too close to the figure. Could you move them out a bit? Also, why y and x are textbf while in the other we have mathbf?

"""Vectors to plot on the Bloch sphere"""
vectors::Vector{Vector{Float64}} = Vector{Vector{Float64}}()
"""Lines to draw on the sphere (points, style, properties)"""
lines::Vector{Tuple{Vector{Vector{Float64}},String,Dict{Symbol,Any}}} =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we have Dict{Symbol, Any}? Can't we infer the type?

Creates a semi-transparent spherical surface at the origin with radius 1.
"""
function _draw_bloch_sphere(ax)
sphere_color = RGBAf(1.0, 0.86, 0.86, 0.2)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the Bloch structure we have defined the sphere_alpha field. But it seems to not been used here. Am I wrong?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, let's check that we use all the fields of Bloch.

fig = Figure()
pos = fig[2, 3]
fig1, ax = @test_logs (:warn,) plot_fock_distribution* 2; library = Val(:Makie), location = pos)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are quite a lot, which is nice, but can you embed them inside a @testset?

@albertomercurio
Copy link
Member

I was also thinking, could you also test it with GLMakie instead of CairoMakie? This should be done without any change, as we support Makie in general. I'm curious to see if the rendering quality is the same and what happens in the interactive windows, since now we can move interactively

@Fe-r-oz
Copy link
Contributor Author

Fe-r-oz commented Jun 3, 2025

Bloch sphere using GLMakie using the current pushed version

Edit: Apologies for the delayed mention. When zooming in beyond 100%, at a certain point, only the axes remain visible in the GLMakie 3D plot, while the sphere itself disappears.

Screenshot_select-area_20250603133522

It appears :rect, :diamond, :utriangle shapes for scatter of points don't appear to render well using GLMakie
Screenshot_select-area_20250603133436

@albertomercurio
Copy link
Member

Really nice!

Let's wait the runtests. There are already some failing like spell check and changelog. For the changelog, you can do it by updating the Changelo.md file and then running make changelog.

The documentation test is still running, but I remember that it was failing. Alway check all the tests with make test and make docs

@Fe-r-oz
Copy link
Contributor Author

Fe-r-oz commented Jun 3, 2025

Thank you very much for your guidance! I will make sure that all the tests pass!

@Fe-r-oz
Copy link
Contributor Author

Fe-r-oz commented Jun 4, 2025

I did make docs/format/changelog/test and the documentation build passed locally. Hopefully, the CI runs will pass here as well! Thank you for reviewing this PR - I appreciate your time and feedback!!

Fe-r-oz and others added 2 commits June 4, 2025 23:50
@ytdHuang
Copy link
Member

ytdHuang commented Jun 4, 2025

Ok, thank you for your hard work.
I will trigger the CI pipeline.

@ytdHuang ytdHuang merged commit 47deb2c into qutip:main Jun 5, 2025
15 of 18 checks passed
@Fe-r-oz Fe-r-oz deleted the fa/resolve branch June 5, 2025 07:02
@albertomercurio albertomercurio mentioned this pull request Jun 5, 2025
6 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement Bloch Sphere rendering

3 participants